import observe from "./observe";

// 6、至此，基本完成了对对象的数据劫持，尽管还是有些问题存在，比如数据的索引和length
// 劫持了对象的这些数据之后，目的是--实现响应式
// 在get的时候收集依赖，在set的时候发布更新

// 5、解决数组的响应式问题
// 关于数组的部分查看 ./util/array-reactive/index.js

// 4、解决3的问题
// 终极目标：让整一个对象的每一个层级的属性都是响应式的
// 弊端：对于数组结构要进行特殊处理
{
  // 为什么会产生对数组的特殊处理？

  //  对于数组的分析： 如果属性的值是一个对象，a:{b:1,c:2}，那么递归的出口就是判断 b和c 不是对象，递归结束
  //      但是如果属性的值是数组，a:[1,2,4]，会进入对a的Observer类对a遍历，再进入第三个阶段defineReactive
  //      defineReactive(a, 0) defineReactive(a, 1) defineReactive(a, 2)
  //      数组的key值还不一样，数组的key值是index，0,1,2...，判断到1，2，4不是对象结构，递归结束

  let obj = [1, 2, 3, 4];
  observe(obj);
  // console.log(obj); // src/images/3-数组结构使用object去响应式的奇怪地方.png
  // 数组的扩展性很强，如果对所有index进行数据劫持会消耗性能
  // obj[1] += 2 // get打印：获取1属性.  set打印：设置1属性，值为4
  // console.log('获取索引为2的值', obj[2]); // get打印：获取2属性

  // obj.push(100); // 不打印

  // obj.pop(); // 不打印

  // obj.splice(0, 1);
  // 获取0属性
  // 获取1属性
  // 设置0属性，值为2
  // 获取2属性
  // 设置1属性，值为3
  // 获取3属性
  // 设置2属性，值为4
  // console.log(obj); // {0:2,1:3,2:4, __ob__: Observer,length:3,set 0, get 0,set 1, get 1,set 2, get 2,}

  // obj.reverse();
  // 获取0属性
  // 获取3属性
  // 设置0属性，值为4
  // 设置3属性，值为1
  // 获取1属性
  // 获取2属性
  // 设置1属性，值为3
  // 设置2属性，值为2

  // 问题在于：
  // obj[4] = 100 // 改变原始不存在的索引值，不打印
  // obj.length = 0 // 改变数组长度，不打印

  // 现在可以来回答一下这个代码块第一行的问题了：为什么会产生对数组的特殊处理？
  // 1、对于新增索引和改变数组的length，是不被观察到的
  //    因为这两个从一开始的observe就没有被observe到
  // 2、数组的变异方法，如 push, pop, shift, unshift, splice, sort, reverse 等）会直接修改数组内部结构（如：length），但不会触发数组索引的 setter，
  // 以上这两点：数组的索引是动态变化的，数组长度会变，索引也会增减。
  // 直接去劫持索引只能监听已有元素的访问和赋值，无法监听长度变化和数组方法带来的变化
  // 3、对数组的响应式劫持有限，数组的索引动态且数量大，vue2不会对所有索引都劫持

  // 解决办法：
  // 1、重新定义数组的变异方法，对数组的变异方法进行拦截，在内部调用原生的变异方法，
  // 2、对数组得特殊做数据劫持
}
{
  // 目标1: 如果这个值是基本数据类型，不设置__ob__；如果是对象类型，则设置__ob__，表示它已经设置成响应式了
  // 目标2: 让对象的属性变成响应式
  // 涉及三个文件./util/obj-reactive/ observe、Observer、defineReactive 顺时针循环调用
  // observe.js  -> Observer.js  -> defineReactive.js
  //     /|\                               \|/
  //     /|\———<————————<———————<——————————\|/
  let obj = {
    name: "soup",
    parson: {
      friend: "rice",
    },
    age: 18,
  };
  observe(obj);
  // console.log(obj);

  // 执行步骤
  // 1.obj进入文件1，判断obj是对象且没有__ob__属性，对obj进行响应式封装。
  // 2.进入文件2，给obj赋值__ob__，再遍历obj的属性，分别给name、person、age数据劫持。
  // 3.进入文件3，给name、person、age分别设置Object.defineProperty，且对于这些属性值设置响应式，就进入文件1。
  // 4.进去文件1，判断name、age不是对象，这两个属性的递归结束；person是对象且没有__ob__属性，对person进行响应式封装。
  // 5.进入文件2，给person赋值__ob__，再遍历person的属性，对friend进行数据劫持。
  // 6.进入文件3，给friend设置Object.defineProperty，且对于friend属性设置响应式，就进入文件1。
  // 7.进去文件1，判断friend是不是对象，递归结束
}

// 3、定义defineReactive函数，使用闭包写法进行数据劫持，而不是临时变量
// **在 defineReactive 中，val 作为闭包变量被 get 和 set 方法引用**
// 弊端：（1）只能劫持对象的第一层属性
{
  // 具体分析在： ./images/1-vue对定义一个非对象的变量它做了什么操作.png
  //            ./images2-vue对定义一个对象的变量它做了什么操作.png
  // 可以得出，1、ref对对象和非对象的响应式实现是不一样的，但是具体是如何不一样的？？
  // 2、从这两个图中，有两个类，Dep、Observer，这两个在数据响应式起什么作用？
}
{
  // let obj = {
  //   name: "soup",
  //   parson: {
  //     like: ["eat", "sleep"],
  //   },
  //   age: 18,
  // };
  // function defineReactive(data, key, val) {
  //   // 如果定义成响应式的时候没有初始化值，则返回对象原来的值
  //   if([...arguments].length === 2) {
  //     val = data[key] // val作为这个data[key]的值存在
  //   }
  //   Object.defineProperty(data, key, {
  //     enumerable: true,
  //     configurable: true,
  //     get() {
  //       console.log("调用get获取值");
  //       return val;
  //     },
  //     set(newVal) {
  //       console.log("调用set设置值");
  //       if (newVal !== val) {
  //         val = newVal;
  //       }
  //     },
  //   });
  // }
  // defineReactive(obj, "name", "soup1");
  // defineReactive(obj, "age", 18);
  // obj.age++; // 输出 调用get获取值 调用get获取值 这条语句相当于 obj.age = obj.age + 1 即赋值又访问
  // console.log(obj.name); // 输出 调用get获取值 soup1
}

// 2、劫持数据：控制了对象的属性的行为，<获取和赋值>行为
// 弊端：这种就需要临时变量
{
  // let obj = {
  //   name: "soup",
  //   parson: {
  //     like: ["eat", "sleep"],
  //   },
  //   age: 18,
  // };
  // let temp;
  // Object.defineProperty(obj, 'name', {
  //   set(value) {
  //     temp = value
  //     console.log('set name');
  //   },
  //   get() {
  //     console.log('get name', this);
  //     return temp
  //   }
  // })
  // obj.name = 'soup1';
  // console.log(obj.name);
}

// 1、Object.defineProperty：为对象赋值
// 可以为对象设置额外隐藏的属性
// {
//   let obj = {
//     name: 'soup',
//     parson: {
//       like: ['eat', 'sleep'],
//     }
//   }
//   Object.defineProperty(obj, 'parson', {
//     value: {
//       like: ['eat', 'sleep'],
//     },
//     enumerable: true,
//     writable: false,
//   })
//   obj.parson.like.push('run');
//   console.log(obj);
// }
